package middleware.p2p.log;

import java.io.StringWriter;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.LinkedList;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.bind.annotation.XmlType;

import middleware.p2p.log.XmlHashtableType.XmlHashtableValue;

@XmlRootElement//(name="vector_clock")
@XmlType(propOrder={"xmlVectorClock"})
@XmlAccessorType(XmlAccessType.NONE)
@XmlSeeAlso(XmlHashtableType.class)
public class VectorClock implements XmlSerializable,Comparable<VectorClock>{
	/**
	 * 	Campo necessario alla serializzazione
	 */
	private static final long serialVersionUID = 5918247592416655679L;
	//@XmlElement
	//@XmlJavaTypeAdapter(XmlHashtableAdapter.class)
	private Hashtable<String, Integer> vclock;
	public VectorClock() {
		this.vclock=new Hashtable<String, Integer>();
	}
	public VectorClock(VectorClock vc) {
		this();
		Enumeration<String> keys=vc.vclock.keys();
		while (keys.hasMoreElements()) {
			String tCid=keys.nextElement();
			setClock(tCid,vc.vclock.get(tCid));
		}
	}
	public synchronized void setClock(String componentId,int clockValue) {
		this.vclock.put(componentId, clockValue);		
	}
	public synchronized VectorClock addEventToClock(String componentId) {
		if (this.vclock.containsKey(componentId)) {
			int newValue=this.vclock.get(componentId)+1;
			this.vclock.put(componentId, newValue);
		} else {
			this.vclock.put(componentId, 1);
		}
		return new VectorClock(this);
	}
	public synchronized VectorClock clone() {
		return new VectorClock(this);
	}
	public synchronized LinkedList<String> getAllKey(){
		LinkedList<String> ret=new LinkedList<String>();
		Enumeration<String> key=this.vclock.keys();
		while (key.hasMoreElements()){
			ret.add(key.nextElement());
		}
		return ret;
	}
	public synchronized int getClock(String componentId) {
		if (this.vclock.containsKey(componentId)) {
			return this.vclock.get(componentId);
		} else {
			return 0;
		}	
	}
	public synchronized void resyncClocks(VectorClock vc,String cid) {
		Enumeration<String> keys=vc.vclock.keys();
		while (keys.hasMoreElements()) {
			String tCid=keys.nextElement();
			if (!tCid.equals(cid)) {
				int tClock=getClock(tCid);
				int remoteClock=vc.vclock.get(tCid);
				if (remoteClock>tClock) {
					setClock(tCid, remoteClock);
				}
			}
		}
	}
	@XmlElement(name="vclock")
	public synchronized XmlHashtableValue[] getXmlVectorClock() {
		Enumeration<String> keys=this.vclock.keys();
		XmlHashtableValue hashrep[]=new XmlHashtableValue[this.vclock.size()];
		int i=0;
		while (keys.hasMoreElements()) {
			String key=keys.nextElement();
			hashrep[i++]=new XmlHashtableType.XmlHashtableValue(key, (Integer)this.vclock.get(key));
		}
		return hashrep;		
	}
	public synchronized void setXmlVectorClock(XmlHashtableValue v[]) {		
		for (int i=0;i<v.length;i++) {
			XmlHashtableValue cvalue=v[i];
			this.vclock.put(cvalue.cid, cvalue.value);
		}		
	}
	public synchronized boolean isCausalRelated(VectorClock right) {		
		Enumeration<String> keys=(this.vclock.size()!=0?this.vclock.keys():right.vclock.keys());
		boolean foundStrict=false;
		while (keys.hasMoreElements()) {
			String currKey=keys.nextElement();
			if (right.getClock(currKey)<this.getClock(currKey)) {
				return false;
			}
			if (!foundStrict) {
					if (right.getClock(currKey)>this.getClock(currKey)) {
						foundStrict=true;
					}
			}
		}
		return foundStrict;
	}
	public String toString() {
		StringBuilder sb= new StringBuilder();
		sb.append("Vector Clock:\n");
		Enumeration<String> keys=this.vclock.keys();
		while (keys.hasMoreElements()) {
			String tValue=keys.nextElement();
			sb.append(tValue +" : "+this.vclock.get(tValue)+"\n");
		}
		return sb.toString();		
	}
	@Override
	public synchronized String toXmlString() {
		try {
			JAXBContext context= JAXBContext.newInstance(this.getClass());
			Marshaller marshaller=context.createMarshaller();
			marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
			marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true);
			StringWriter sw=new StringWriter();			
			marshaller.marshal(this, sw);
			return sw.toString();
		} catch (JAXBException jaxbe) {
			jaxbe.printStackTrace();
			return "";
		}		
	}
	public synchronized boolean detectGap(VectorClock right) {
		Enumeration<String> keys=(this.vclock.size()!=0?this.vclock.keys():right.vclock.keys());
		int nextValue=0;
		while (keys.hasMoreElements()) {
			String currKey=keys.nextElement();
			if ((right.getClock(currKey)-this.getClock(currKey))>=1) {	
					nextValue++;
			}
		}		
		return (nextValue>1);
	}	
	public synchronized boolean detectProximity(VectorClock right) {
		Enumeration<String> keys=(this.vclock.size()!=0?this.vclock.keys():right.vclock.keys());
		int nextValue=0;
		while (keys.hasMoreElements()) {
			String currKey=keys.nextElement();
			if ((right.getClock(currKey)-this.getClock(currKey))==1) {	
					nextValue++;
			}
		}		
		return (nextValue==1);
	}	
	@Override
	public synchronized int compareTo(VectorClock o) {
		//Check if this<o
		if (this.isCausalRelated(o)) {
			return -1;
		} else if (o.isCausalRelated(this)) {
			return 1;
		} else {
			return 0;
		}
	}
}
